#pragma once
#include "stdafx.h"
#include "afxpropertygridctrl.h"
#include <list>
#include <string>
#include <map>
#include <boost/typeof/typeof.hpp>

template < class ValueType >
struct VariantTConverter
{
	static _variant_t Convert( const ValueType& value )
	{
		return (_variant_t) value;
	}
	static ValueType ReverseConvert( const _variant_t& value )
	{
		return (ValueType) value;
	}
} ;

template <>
struct VariantTConverter<std::wstring>
{
	static _variant_t Convert( const std::wstring& value )
	{
		return (_variant_t) (_bstr_t) value.c_str();
	}
	static std::wstring ReverseConvert( const _variant_t& value )
	{
		return (std::wstring) (const wchar_t*) (_bstr_t) value;
	}
} ;

template <>
struct VariantTConverter<std::string>
{
	static _variant_t Convert( const std::string& value )
	{
		return (_variant_t) (_bstr_t) value.c_str();
	}
	static std::string ReverseConvert( const _variant_t& value )
	{
		return (std::string) (const char*) (_bstr_t) value;
	}
} ;

class CObjectBindablePropertyGridProperty;

class CObjectBindablePropertyGridCtrl :
	public CMFCPropertyGridCtrl
{
	friend class CObjectBindablePropertyGridProperty;
protected:
	static const UINT MY_WM_NEEDREBUILDPROPERTIES = WM_USER+1500;
	class Property
	{
		friend class CObjectBindablePropertyGridCtrl;
		friend class CObjectBindablePropertyGridProperty;
	private:
		class VarBase
		{
		public:
			virtual _variant_t GetVar() = 0;
			virtual void OnValueChanged( const _variant_t& newValue, bool& isChangeValid,
				std::wstring& errorMessage, bool& needRebuildProperties) = 0;
			virtual ~VarBase(){}
		} ;

		template < class ValueType >
		class PropertyVar : public VarBase
		{
			ValueType& value;
		public:
			typedef boost::function<void(const ValueType& newValue, bool& isChangeValid,
				std::wstring& errorMessage, bool& needRebuildProperties)> OnValueChangedCallbackType;
		private:
			OnValueChangedCallbackType onValueChanged;
		public:
			PropertyVar( ValueType& value_, const OnValueChangedCallbackType& onValueChanged_ )
				: value(value_), onValueChanged(onValueChanged_){}
			virtual _variant_t GetVar()
			{
				return VariantTConverter<ValueType>::Convert( value );
			}
			
			virtual void OnValueChanged( const _variant_t& newValue, bool& isChangeValid,
				std::wstring& errorMessage, bool& needRebuildProperties)
			{
				ValueType nv = VariantTConverter<ValueType>::ReverseConvert( newValue );
				if ( onValueChanged )
					onValueChanged( nv, isChangeValid, errorMessage, needRebuildProperties );
				if ( isChangeValid )
				{
					value = nv;
				}
			}
		} ;

		template < class ValueType >
		class ReadOnlyPropertyVar : public VarBase
		{
			_variant_t variant;
		public:
			ReadOnlyPropertyVar( const ValueType& value_ )
			{
				variant = VariantTConverter<ValueType>::Convert( value_ );
			}
			virtual _variant_t GetVar()
			{
				return variant;
			}
			
			virtual void OnValueChanged( const _variant_t& newValue, bool& isChangeValid,
				std::wstring& errorMessage, bool& needRebuildProperties)
			{
				//assert( false );
			}
		} ;

		bool editable, isGroup, isValueList;
		std::list< Property > chidren;
		std::list< _variant_t > options;
		boost::shared_ptr<VarBase> var;
		CString name, description;

		Property():editable(true), isGroup(false){}

	public:
		static Property Group( const std::wstring& name,
			bool isValueList = false )
		{
			Property prop;
			prop.name = name.c_str();
			prop.isValueList = isValueList;
			prop.isGroup = true;
			prop.editable = false;
			return prop;
		}

		static Property Group( const wchar_t* name,
			bool isValueList = false )
		{
			return Group( std::wstring(name), isValueList );
		}

		template < class ValueType >
		static Property Value( const std::wstring& name, ValueType& value,
			const std::wstring& description = std::wstring(),
			const typename PropertyVar<ValueType>::OnValueChangedCallbackType& onValueChanged
			 = typename PropertyVar<ValueType>::OnValueChangedCallbackType())
		{
			Property prop;
			prop.var.reset( new PropertyVar<ValueType>( value, onValueChanged ) );
			prop.name = name.c_str();
			prop.description = description.c_str();
			return prop;
		}
		
		template < class ValueType >
		static Property Value( const wchar_t* name, ValueType& value,
			const wchar_t* description = L"",
			const typename PropertyVar<ValueType>::OnValueChangedCallbackType& onValueChanged
			 = typename PropertyVar<ValueType>::OnValueChangedCallbackType())
		{
			return Value( std::wstring(name), value, std::wstring(description), onValueChanged );
		}

		template < class ValueType >
		static Property ReadOnly( const std::wstring& name, const ValueType& value,
			const std::wstring& description = std::wstring())
		{
			Property prop;
			prop.var.reset( new ReadOnlyPropertyVar<ValueType>( value ) );
			prop.name = name.c_str();
			prop.description = description.c_str();
			prop.editable = false;
			return prop;
		}

		template < class ValueType >
		static Property ReadOnly( const wchar_t* name, const ValueType& value,
			const wchar_t* description = L"" )
		{
			return ReadOnly( std::wstring(name), value, std::wstring(description) );
		}

		Property& AllowEdit( bool allowEdit )
		{
			editable = allowEdit;
			return *this;
		}

		template <class IteratorType>
		Property& SetOptions( const IteratorType& begin, const IteratorType& end )
		{
			options.clear();
			for ( IteratorType iter = begin; iter != end; ++ iter )
			{
				options.push_back( VariantTConverter< BOOST_TYPEOF(*iter) >::Convert(*iter) );
			}
			return *this;
		}

		template <class ObjectType>
		Property& AddOption( const ObjectType& option )
		{
			options.push_back( VariantTConverter< ObjectType >::Convert(option) );
			return *this;
		}

		Property& operator & ( const Property& prop )
		{
			chidren.push_back( prop );
			return *this;
		}
	};

	class PropertiesBinderBase
	{
	public:
		virtual void BuildProperties( CObjectBindablePropertyGridCtrl* pCtrl ) = 0;
		virtual ~PropertiesBinderBase(){}
	} ;
	
	template < class ObjectType >
	class PropertiesBinder : public PropertiesBinderBase
	{
		ObjectType& obj;
	public:
		PropertiesBinder( ObjectType& obj_ ): obj(obj_) {}
		virtual void BuildProperties( CObjectBindablePropertyGridCtrl* pCtrl )
		{
			Property root;
			obj.GetProperties( root );
			pCtrl->RemoveAll();
			for ( std::list< Property >::iterator iter = root.chidren.begin(), end = root.chidren.end();
				iter!=end; ++iter )
			{
				pCtrl->AddProperty( pCtrl->RecursiveDealWithProperty( *iter ) );
			}
		}
	} ;

	friend class PropertiesBinderBase;

protected:
	//typedef std::map< CMFCPropertyGridProperty*, shared_ptr<Property::VarBase> > PropertyMapType;
	//PropertyMapType propertyMap;
	boost::shared_ptr<PropertiesBinderBase> propertyBinder;
public:
	CObjectBindablePropertyGridCtrl(void);
	~CObjectBindablePropertyGridCtrl(void);
protected:

	CMFCPropertyGridProperty* RecursiveDealWithProperty( Property& prop );

public:
	/**
	 * Bind an object to this property grid.
	 * ObjectType must have a member function: GetProperties<Property>( Property& root ).
	 * @param obj The object to bind.
	 */
	template < class ObjectType >
	void BindObject( ObjectType& obj )
	{
		propertyBinder.reset( new PropertiesBinder<ObjectType>( obj ) );
		propertyBinder->BuildProperties( this );
	}

protected:
	DECLARE_MESSAGE_MAP()

	virtual BOOL OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult);
};

class CObjectBindablePropertyGridProperty : public CMFCPropertyGridProperty
{
	DECLARE_DYNAMIC(CObjectBindablePropertyGridProperty)
	friend class CObjectBindablePropertyGridCtrl;
protected:
	boost::shared_ptr<CObjectBindablePropertyGridCtrl::Property::VarBase> var;
	HWND theGridCtrl;
public:
	// Group constructor
	CObjectBindablePropertyGridProperty(const CString& strGroupName, DWORD_PTR dwData = 0, BOOL bIsValueList = FALSE)
		:CMFCPropertyGridProperty( strGroupName, dwData, bIsValueList ){}

	// Simple property
	CObjectBindablePropertyGridProperty(const CString& strName, const COleVariant& varValue, LPCTSTR lpszDescr = NULL, DWORD_PTR dwData = 0,
		LPCTSTR lpszEditMask = NULL, LPCTSTR lpszEditTemplate = NULL, LPCTSTR lpszValidChars = NULL)
		:CMFCPropertyGridProperty( strName, varValue, lpszDescr, dwData,
			lpszEditMask, lpszEditTemplate, lpszValidChars ){}

	virtual BOOL OnUpdateValue();
};
